﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Web;
using System.Xml;

namespace Bierdopje.API
{
    /// <summary>
    /// Main entry point of the library
    /// 
    /// Suply an ApiKey and fetch a show.
    /// </summary>
    public class ApiLibrary
    {
        // Location of the API
        private const string cBaseUrl = "http://api.bierdopje.com/"; 
        // Xpath to the results, | construction used due to inconsistent API
        private const string cResultNodes = @"/bierdopje/response/result|/bierdopje/response/results/result|/bierdopje/response/results/genre"; 
        // Xpath to Error code 
        private const string cErrorCodeNode = @"/bierdopje/response/error_code";
        // Xpath to Error message
        private const string cErrorMsgNode = @"/bierdopje/response/error_msg";
        // Xpath to single response item
        private const string cResponseNode = @"/bierdopje/response";
        // Replace spaces in search string with _
        private const bool cReplaceSpaces = true;

        private string m_ApiKey;

        /// <summary>
        /// The supplied API Key
        /// </summary>
        public string ApiKey
        {
            get { return m_ApiKey; }
        }

        /// <summary>
        /// Creates an ApiLibrary object. This object is the starting point of the API.
        /// You need an APIKey from bierdopje.com to use this class.
        /// 
        /// See this URL for more info: http://bierdopje.com/forum/algemeen-developers
        /// </summary>
        /// <param name="apiKey">The API Key from bierdopje.com</param>
        public ApiLibrary(string apiKey)
        {
            m_ApiKey = apiKey;
            m_Cache = new ApiCache();
        }

        private ApiCache m_Cache;

        #region API Methods
        /// <summary>
        /// Gets an ApiShow object through the API using a showID
        /// </summary>
        /// <param name="showId">A know showID</param>
        /// <returns>An ApiShow object, showID will be empty if the show isn't found</returns>
        public ApiShow GetShowById(int showId)
        {
            XmlDocument _XDoc = GetXMLApiResonse("GetShowById", new string[] {Convert.ToString(showId)});

            XmlNode _Node = _XDoc.SelectSingleNode(cResponseNode);

            return ParseShowNode(_Node);
        }

        /// <summary>
        /// Gets an ApiShow object through the API using a TVDB id
        /// </summary>
        /// <param name="showId">A know TVDB id</param>
        /// <returns>An ApiShow object, showID will be empty if the show isn't found</returns>
        public ApiShow GetShowByTVDBID(int showId)
        {
            XmlDocument _XDoc = GetXMLApiResonse("GetShowByTVDBID", new string[] {Convert.ToString(showId)});

            XmlNode _Node = _XDoc.SelectSingleNode(cResponseNode);

            return ParseShowNode(_Node);
        }

        /// <summary>
        /// Returns a collection of ApiShow objects matching the name parameter
        /// </summary>
        /// <param name="name">The show to search for</param>
        /// <returns>A list of ApiShow objects</returns>
        public List<ApiShow> FindShowByName(string name)
        {
            #pragma warning disable 162
            if (cReplaceSpaces)
            {
                // Spaces need to be replaced with _
                name = name.Replace(' ', '_');
            }
            #pragma warning restore 162

            string justAlphaNumeric = "";
            name = name.ToLower();
            int minA = (int)'a';
            int maxZ = (int)'z';
            int min0 = (int)'0';
            int max9 = (int)'9';
            foreach (Char _char in name)
            {
                if ((_char >= minA && _char <= maxZ) || (_char >= min0 && _char <= max9) || (_char == '_'))
                    justAlphaNumeric += _char.ToString();
            }

            List<ApiShow> _ApiShows = new List<ApiShow>();
            
            XmlDocument _XDoc;
            try
            {
                _XDoc = GetXMLApiResonse("FindShowByName", new string[] { justAlphaNumeric });
            }
            catch (EApiCallNoResults)
            {
                return _ApiShows;
            }

            foreach (XmlNode _Node in _XDoc.SelectNodes(cResultNodes))
            {
                _ApiShows.Add(ParseShowNode(_Node));
            }

            return _ApiShows;
        }
        #endregion

        #region Internal API Methods (promote to public for direct API access)
        /// <summary>
        /// Get all episodes for a season
        /// </summary>
        /// <param name="showId">Show to get the episodes for</param>
        /// <param name="season">The season to retrieve episodes for</param>
        /// <returns>A list of ApiEpisodes</returns>
        internal List<ApiEpisode> GetEpisodesForSeason(int showId, int season)
        {
            XmlDocument _XDoc;
            try
            {
                _XDoc = GetXMLApiResonse("GetEpisodesForSeason", new string[] { Convert.ToString(showId), Convert.ToString(season) });
            }
            catch (EApiCallNoResults)
            {

                return new List<ApiEpisode>();
            }

            List<ApiEpisode> _ApiEpisodes = new List<ApiEpisode>();

            foreach (XmlNode _Node in _XDoc.SelectNodes(cResultNodes))
            {
                _ApiEpisodes.Add(ParseEpisodeNode(_Node));
            }

            return _ApiEpisodes;
        }

        /// <summary>
        /// Get all episodes for a Show
        /// </summary>
        /// <param name="showId">The bierdopje showID</param>
        /// <returns>A list of ApiEpisodes</returns>
        internal List<ApiEpisode> GetAllEpisodesForShow(int showId)
        {
            XmlDocument _XDoc = GetXMLApiResonse("GetAllEpisodesForShow", new string[] {Convert.ToString(showId)});

            List<ApiEpisode> _ApiEpisodes = new List<ApiEpisode>();

            foreach (XmlNode _Node in _XDoc.SelectNodes(cResultNodes))
            {
                _ApiEpisodes.Add(ParseEpisodeNode(_Node));
            }

            return _ApiEpisodes;
        }

        /// <summary>
        /// Gets and episode using its episode ID
        /// </summary>
        /// <param name="episodeId">Know episode ID</param>
        /// <returns>ApiEpisode</returns>
        internal ApiEpisode GetEpisodeById(int episodeId)
        {
            XmlDocument _XDoc = GetXMLApiResonse("GetEpisodeById", new string[] {Convert.ToString(episodeId)});

            XmlNode _Node = _XDoc.SelectSingleNode(cResponseNode);

            return ParseEpisodeNode(_Node);
        }

        /// <summary>
        /// Gets all subs for an Episode specifying a language
        /// </summary>
        /// <param name="episodeId">Known episode ID</param>
        /// <param name="lang">nl or en</param>
        /// <returns>ApiSub object</returns>
        internal List<ApiSub> GetAllSubsForEpisode(int episodeId, string lang)
        {
            XmlDocument _XDoc = GetXMLApiResonse("GetAllSubsForEpisode", new string[] {Convert.ToString(episodeId),lang});

            List<ApiSub> _ApiSubs = new List<ApiSub>();

            foreach (XmlNode _Node in _XDoc.SelectNodes(cResultNodes))
            {
                _ApiSubs.Add(ParseSubtitleNode(_Node));
            }

            return _ApiSubs;
        }
        #endregion
        
        #region API Communication
        /// <summary>
        /// Retrieves the XML document from bierdopje. Before returning the XML file
        /// the following is done:
        /// 
        /// - Convert HTTP entities to XML
        /// - Check for bierdopje errors and throw an appropriate exception
        /// - Check the status field for true (to see if there are records)
        /// - Sanity check
        /// </summary>
        /// <param name="command">The bierdopje API command, see: http://bierdopje.com/forum/algemeen-developers/topic/2981-API---Versie-1/last</param>
        /// <param name="parameters">Parameters for the command, each command should be in a seperate string</param>
        /// <returns>XmlDocument object, to ensure a well formed document</returns>
        private XmlDocument GetXMLApiResonse(string command, string[] parameters)
        {
            // Construct a GET response and download response
            string responseFromServer = GetApiResult(command, parameters);

            XmlDocument _XDoc = new XmlDocument();

            // Cleanup XML and load into a XmlDocument object
            _XDoc.LoadXml(responseFromServer);

            // Check for bierdopje errors
            XmlNode _XNErrorCode = _XDoc.SelectSingleNode(cErrorCodeNode);
            XmlNode _XNErrorMessage = _XDoc.SelectSingleNode(cErrorMsgNode);
            if (_XNErrorCode != null)
            {
                int errorCode = Convert.ToInt32(_XNErrorCode.InnerText);
                string errorMessage = "";

                if (_XNErrorMessage != null)
                    errorMessage = _XNErrorMessage.InnerText;

                throw new EApiBierdopjeException(errorCode, errorMessage);
            }

            // Retrieve status field and do sanity check
            XmlNodeList _XNL = _XDoc.SelectNodes(@"/bierdopje/response/status");
            if (_XNL.Count > 0)
            {
                if (_XNL[0].InnerText.Equals("false"))
                {
                    throw new EApiCallNoResults();
                }
                else if (!_XNL[0].InnerText.Equals("true"))
                {
                    throw new EApiParseException();
                }
            }
            else
                throw new EApiParseException();

            return _XDoc;
        }

        private string GetApiResult(string command, string[] parameters)
        {
            string apiUrl = CreateAPIUrl(command, parameters);

            if (command.Equals("FindShowByName")
                ||
                command.Equals("GetShowByTVDBID")
                ||
                command.Equals("GetShowById"))
            {
                string cache_result = m_Cache.GetItemFromCache(apiUrl);

                if (cache_result.Equals(""))
                {
                    string responseFromServer = XMLHelper.XMLCleanup(HTTPHelper.GetHTTPResponse(apiUrl));

                    m_Cache.StoreItemInCache(apiUrl, responseFromServer);

                    return responseFromServer;
                }
                else
                {
                    return cache_result;
                }
            }
            else
            {
                return XMLHelper.XMLCleanup(HTTPHelper.GetHTTPResponse(apiUrl));
            }
        }

        /// <summary>
        /// Constructs a well formed and encoded api url
        /// </summary>
        /// <param name="command">The command to be executed</param>
        /// <param name="parameters">The parameters of the command, seperated by a @"/"</param>
        /// <returns>A well formed url</returns>
        private string CreateAPIUrl(string command, string[] parameters)
        {
            string newCommand = command.Replace("/", "");

            string _UrlFormattedParam = "";
            for (int i = 0; i < parameters.Length; i++)
            {
                if (i == 0)
                {
                    _UrlFormattedParam = parameters[i].Replace(@"/", "");
                }
                else
                {
                    _UrlFormattedParam = _UrlFormattedParam + @"/" + parameters[i].Replace(@"/", "");
                }
            }

            return cBaseUrl + ApiKey + "/" + newCommand + "/" + HTTPHelper.HTTPEncodeString(_UrlFormattedParam);
        }
        #endregion

        #region Parse code
        private ApiShow ParseShowNode(XmlNode showNode)
        {
            ApiShow _Show = new ApiShow(this);

            _Show.Name = XMLHelper.ExtractStringFromNode(showNode, "showname");
            _Show.AirTime = XMLHelper.ExtractStringFromNode(showNode, "airtime");
            _Show.Episodes = XMLHelper.ExtractIntFromNode(showNode, "episodes");
            _Show.Favorites = XMLHelper.ExtractIntFromNode(showNode, "favorites");
            _Show.FirstAired = XMLHelper.ExtractDateTimeFromNode(showNode, "firstaired");
            _Show.LastAired = XMLHelper.ExtractDateTimeFromNode(showNode, "firstaired");
            _Show.Link = XMLHelper.ExtractStringFromNode(showNode, "showlink");
            _Show.Network = XMLHelper.ExtractStringFromNode(showNode, "network");
            _Show.NextEpisode = XMLHelper.ExtractDateTimeFromNode(showNode, "nextepisode");
            _Show.RunTime = XMLHelper.ExtractIntFromNode(showNode, "runtime");
            _Show.Score = XMLHelper.ExtractDecimalFromNode(showNode, "score");
            _Show.Seasons = XMLHelper.ExtractIntFromNode(showNode, "seasons");
            _Show.ShowID = XMLHelper.ExtractIntFromNode(showNode, "showid");
            _Show.ShowStatus = XMLHelper.ExtractStringFromNode(showNode, "showstatus");
            _Show.Summary = XMLHelper.ExtractStringFromNode(showNode, "summary");
            _Show.Translators = XMLHelper.ExtractBoolFromNode(showNode, "has_translators");
            _Show.TVDBID = XMLHelper.ExtractIntFromNode(showNode, "tvdbid");
            return _Show;
        }

        private ApiEpisode ParseEpisodeNode(XmlNode episodeNode)
        {
            ApiEpisode _Episode = new ApiEpisode(this);

            _Episode.AirDate = XMLHelper.ExtractDateTimeFromNode(episodeNode, "airdate");
            _Episode.ENGSubPresent = XMLHelper.ExtractBoolFromNode(episodeNode, "subsen");
            _Episode.NLSubPresent = XMLHelper.ExtractBoolFromNode(episodeNode, "subsnl");
            _Episode.Episode = XMLHelper.ExtractIntFromNode(episodeNode, "episode");
            _Episode.EpisodeNumber = XMLHelper.ExtractIntFromNode(episodeNode, "epnumber");
            _Episode.FormattedEpisodeString = XMLHelper.ExtractStringFromNode(episodeNode, "formatted");
            _Episode.ID = XMLHelper.ExtractIntFromNode(episodeNode, "episodeid");
            _Episode.Link = XMLHelper.ExtractStringFromNode(episodeNode, "episodelink");
            _Episode.Score = XMLHelper.ExtractDecimalFromNode(episodeNode, "score");
            _Episode.Season = XMLHelper.ExtractIntFromNode(episodeNode, "season");
            _Episode.SpecialEpisode = XMLHelper.ExtractBoolFromNode(episodeNode, "is_special");
            _Episode.Summary = XMLHelper.ExtractStringFromNode(episodeNode, "summary");
            _Episode.Title = XMLHelper.ExtractStringFromNode(episodeNode, "title");
            _Episode.TVDBID = XMLHelper.ExtractIntFromNode(episodeNode, "tvdbid");
            _Episode.Votes = XMLHelper.ExtractIntFromNode(episodeNode, "votes");
            _Episode.WorkInProgress = XMLHelper.ExtractBoolFromNode(episodeNode, "wip");
            _Episode.WorkInProgressPercentage = XMLHelper.ExtractDecimalFromNode(episodeNode, "wippercentage");
            _Episode.WorkInProgressUser = XMLHelper.ExtractStringFromNode(episodeNode, "wipuser");

            return _Episode;
        }

        private ApiSub ParseSubtitleNode(XmlNode subtitleNode)
        {
            ApiSub _Subtitle = new ApiSub();

            _Subtitle.DownloadCount = XMLHelper.ExtractIntFromNode(subtitleNode, "numdownloads");
            _Subtitle.DownloadLink = XMLHelper.ExtractStringFromNode(subtitleNode, "downloadlink");
            _Subtitle.FileName = XMLHelper.ExtractStringFromNode(subtitleNode, "filename");
            _Subtitle.FileSize = XMLHelper.ExtractIntFromNode(subtitleNode, "filesize");
            _Subtitle.PubDate = XMLHelper.ExtractDateTimeFromNode(subtitleNode, "pubdate");
            _Subtitle.RepliesCount = XMLHelper.ExtractIntFromNode(subtitleNode, "numreplies");
            _Subtitle.Uploader = XMLHelper.ExtractStringFromNode(subtitleNode, "uploader");

            return _Subtitle;
        }
        #endregion
    }
}
